/**
 * Copyright (c) 2020 Coder League
 * All rights reserved.
 *
 * File：AliyunInterfaceService.java
 * History:
 *         2020年12月22日: Initially created, Chrise.
 */
package club.coderleague.cfss.service.interfaces;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;

import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.DefaultProfile;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import club.coderleague.cfss.cache.CacheManager;
import club.coderleague.cfss.config.properties.AliyunSettings;
import club.coderleague.cfss.dao.NotifyTemplateConfigDao;
import club.coderleague.cfss.domain.beans.SystemConfig;
import club.coderleague.cfss.domain.beans.VercodeBound;
import club.coderleague.cfss.domain.entities.Notifytemplateconfigs;
import club.coderleague.cfss.domain.enums.NotifyType;
import club.coderleague.cfss.util.CommonUtil;

/**
 * 阿里云接口服务。
 * @author Chrise
 */
@Service
@SuppressWarnings("unchecked")
public class AliyunInterfaceService {
	private static final Logger logger = LoggerFactory.getLogger(AliyunInterfaceService.class);
	private static final Pattern PHONE_PATTERN = Pattern.compile("^1\\d{10}$");
	private static final String CACHE_KEY_PREFIX = "vercode_cache_";
	private static final String LOCAL_NOTIFY_KEY_PREFIX = "local_notify_";
	private static final String TPL_PARAM_CODE = "code";
	
	@Autowired
	private AliyunSettings aliyunSettings;
	@Autowired
	private CacheManager cacheManager;
	@Autowired
	private ThreadPoolTaskScheduler taskScheduler;
	@Autowired
	private NotifyTemplateConfigDao ntcDao;
	
	/**
	 * 发送验证码。
	 * @author Chrise 2020年12月22日
	 * @param phone 手机号。
	 * @param session 会话标识。
	 * @return 发送结果。
	 */
	public boolean sendVercode(String phone, String session) {
		try {
			// 验证手机号
			if (!PHONE_PATTERN.matcher(phone).matches()) return false;
			
			// 启动验证码发送任务
			this.taskScheduler.schedule(new Runnable() {
				public void run() {
					sendVercodeInternal(phone, session);
				}
			}, new Date());
			
			return true;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
		
		return false;
	}
	
	/**
	 * 获取缓存的验证码绑定对象。
	 * @author Chrise 2020年12月22日
	 * @param session 会话标识。
	 * @return 缓存的验证码绑定对象。
	 */
	public VercodeBound getCachedVercode(String session) {
		// 获取缓存验证码绑定对象
		String key = CACHE_KEY_PREFIX + session;
		VercodeBound cached = (VercodeBound)this.cacheManager.getObject(key, true);
		return cached;
	}
	
	/**
	 * 清除缓存。
	 * @author Chrise 2020年12月22日
	 * @param session 会话标识。
	 */
	public void clearCache(String session) {
		// 清除缓存
		String key = CACHE_KEY_PREFIX + session;
		this.cacheManager.setObject(key, null, 0L);
	}
	
	/**
	 * 获取本地通知。
	 * @author Chrise 2020年12月22日
	 * @param phone 手机号。
	 * @return 本地通知内容。
	 */
	public Object getLocalNotify(String phone) {
		Object notify = this.cacheManager.getObject(LOCAL_NOTIFY_KEY_PREFIX + phone, true);
		return (notify == null ? "" : notify);
	}
	
	/**
	 * 发送验证码。
	 * @author Chrise 2020年12月22日
	 * @param phone 手机号。
	 * @param session 会话标识。
	 */
	private void sendVercodeInternal(String phone, String session) {
		try {
			// 获取系统配置
			SystemConfig sc = (SystemConfig)this.cacheManager.getObject(SystemConfig.CACHE_KEY, false);
			if (sc == null || sc.getVercodetimeout() == null) {
				logger.error("The vercode send failed because system config not found.");
				return;
			}
			
			// 生成验证码
			String vercode = this.genVercode();
			
			// 缓存验证码绑定对象
			this.cacheManager.setObject(CACHE_KEY_PREFIX + session, new VercodeBound(phone, vercode), sc.getVercodetimeout(), TimeUnit.MINUTES);
			
			// 构造参数值集合
			Map<String, String> values = new HashMap<String, String>();
			values.put(TPL_PARAM_CODE, vercode);
			
			// 生成短信内容
			Map<String, String> params = new HashMap<String, String>();
			String code = this.genSMSContent(NotifyType.VERIFICATION_CODE, values, params);
			if (CommonUtil.isEmpty(code)) {
				logger.error("The vercode [{}] send failed because sms template not found.", vercode);
				return;
			}
			
			// 发送验证码短信
			if (this.aliyunSettings.getLocalNotify()) {
				// 缓存通知参数
				this.cacheManager.setObject(LOCAL_NOTIFY_KEY_PREFIX + phone, params, this.aliyunSettings.getLocalNotifyTimeout(), TimeUnit.MINUTES);
				
				logger.info("The vercode is [{}] and sms content is [{}].", vercode, params);
			} else this.sendSMS(sc, phone, code, params);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}
	
	/**
	 * 生成验证码。
	 * @author Chrise 2020年12月22日
	 * @return 验证码字符串。
	 */
	private String genVercode() {
		StringBuilder builder = new StringBuilder();
		Random r = new Random(System.currentTimeMillis());
		for (int i = 0; i < 6; i ++) {
			builder.append(r.nextInt(10));
		}
		return builder.toString();
	}
	
	/**
	 * 生成短信内容。
	 * @author Chrise 2020年12月22日
	 * @param type 通知类型。
	 * @param values 参数值集合。
	 * @param params 模板参数集合。
	 * @return 模板代码。
	 * @throws IOException 输入输出错误。
	 */
	private String genSMSContent(NotifyType type, Map<String, String> values, Map<String, String> params) 
		throws IOException {
		// 查询模板配置
		Notifytemplateconfigs ntc = this.ntcDao.queryTemplate(type);
		if (ntc == null) return null;
		
		// 构造模板参数
		Map<String, String> temp = new ObjectMapper().readValue(ntc.getTplparam(), Map.class);
		Iterator<Entry<String, String>> it = temp.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String, String> e = it.next();
			params.put(e.getKey(), values.get(e.getValue()));
		}
		
		return ntc.getTplcode();
	}
	
	/**
	 * 发送短信。
	 * @author Chrise 2020年12月22日
	 * @param config 系统配置对象。
	 * @param phone 手机号。
	 * @param code 模板代码。
	 * @param params 模板参数。
	 * @throws JsonProcessingException JSON处理错误。
	 * @throws ClientException 客户端错误。
	 * @throws IOException 输入输出错误。
	 */
	private void sendSMS(SystemConfig config, String phone, String code, Map<String, String> params) 
		throws JsonProcessingException, ClientException, IOException {
		String param = new ObjectMapper().writeValueAsString(params);
		
		DefaultProfile profile = DefaultProfile.getProfile("default", config.getAyappid(), config.getAyappkey());
		IAcsClient client = new DefaultAcsClient(profile);
		
		CommonRequest request = new CommonRequest();
		request.setSysProtocol(ProtocolType.HTTPS);
		request.setSysMethod(MethodType.POST);
		request.setSysDomain(this.aliyunSettings.getDomain());
		request.setSysVersion(this.aliyunSettings.getVersion());
		request.setSysAction(this.aliyunSettings.getAction());
		request.putQueryParameter("PhoneNumbers", phone);
		request.putQueryParameter("SignName", config.getAysign());
		request.putQueryParameter("TemplateCode", code);
		request.putQueryParameter("TemplateParam", param);
		
		CommonResponse response = client.getCommonResponse(request);
		Map<String, String> result = new ObjectMapper().readValue(response.getData(), Map.class);
		if (!"OK".equals(result.get("Code"))) {
			logger.error("The sms to [{}] send failed because [{}].", phone, result);
		}
	}
}
